import { Scene } from '@babylonjs/core/scene';
import { HardLineMaterial, IHardLineMaterialOptions } from './hard-line-material';


/**
 * 间隔线材质参数对象
 * @param uScaleFactor u向（线条方向）uv缩放因数
 * @param discardStart 线段起始端丢弃长度（精度导致取余后有残余渲染片元，默认0.01）
 * @param discardEnd 线段终端丢弃长度（精度导致取余后有残余渲染片元，默认0.01）
 */
interface IStripsLineMaterialOptions extends IHardLineMaterialOptions {
    uScaleFactor?: number,
    discardStart?: number,
    discardEnd?: number
}


/**
 * 间隔线材质
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
class StripsLineMaterial extends HardLineMaterial {

    private _uScaleFactor!: number;
    private _discardStart!: number;
    private _discardEnd!: number;

    constructor(name: string, scene: Scene, options?: Partial<IStripsLineMaterialOptions>) {
        super(name, scene, options);
    }

    protected initializeVariable(options?: Partial<IStripsLineMaterialOptions>): void {
        super.initializeVariable(options);
        const op = options || {};
        this._uScaleFactor = op.uScaleFactor || 1;
        this._discardStart = op.discardStart || 0.01;
        this._discardEnd = op.discardEnd || 0.01;
    }

    /**
     * 获取u向uv缩放因数
     */
    public get uScaleFactor(): number {
        return this._uScaleFactor;
    }

    /**
     * 设置u向uv缩放因数
     */
    public set uScaleFactor(factor: number) {
        this._uScaleFactor = factor;
        this.setFloat('uScaleFactor', factor);
    }

    /**
     * 获取起始端丢弃长度
     */
    public get discardStart(): number {
        return this._discardStart;
    }

    /**
     * 设置起始端丢弃长度
     */
    public set discardStart(length: number) {
        this._discardStart = length;
        this.setFloat('discardStart', length);
    }

    /**
     * 获取终点端丢弃长度
     */
    public get discardEnd(): number {
        return this._discardEnd;
    }

    /**
     * 设置终点端丢弃长度
     */
    public set discardEnd(length: number) {
        this._discardEnd = length;
        this.setFloat('discardEnd', length);
    }

    protected initializeUniforms(): void {
        super.initializeUniforms();
        this.options.attributes.push('count');
        this.options.uniforms.push('uScaleFactor', 'discardStart', 'discardEnd');
        this.setFloat('uScaleFactor', this._uScaleFactor);
        this.setFloat('discardStart', this._discardStart);
        this.setFloat('discardEnd', this._discardEnd);
    }

    protected initializeShader(): void {
        this.shaderPath = {
            vertexSource: `
            precision highp float;
            attribute vec3 position;
            attribute vec3 nextPosition;
            attribute float offset;
            attribute float lineDistance;
            attribute float count;
            uniform mat4 worldView;
            uniform mat4 projection;
            uniform float width;
            uniform float perspectiveFactor;
            uniform int sizeAttenuation;
            uniform float uScaleFactor;
            uniform float depthOffset;
            varying vec3 vColor;
            varying float vDistance;
            varying float vDrop;
            varying vec2 vuv;
            void main(){
                vec4 mvPosition=worldView*vec4(position,1.0);
                vec4 pmvPosition=projection*mvPosition;

                vec4 mvNextPosition=worldView*vec4(nextPosition,1.0);
                vec4 nxtP=projection*mvNextPosition;

                vec4 curP=pmvPosition;
                vec3 nxt=vec3(nxtP.xyz/nxtP.w);
                vec3 cur=vec3(curP.xyz/curP.w);
                vec3 dirOri=nxt-cur;
                vec3 look=vec3(0.0,0.0,1.0);

                vec3 dirCC=mvPosition.xyz/mvPosition.w;
                vec3 dirCN=mvNextPosition.xyz/mvNextPosition.w;
                float dotC=dot(look,dirCC);
                float dotN=dot(look,dirCN);
                if(dotC*dotN<0.){
                    if(projection[3][3]==0.0){
                        look=vec3(0.,0.,-1.);
                    }
                }

                float aspect=projection[0][0]/projection[1][1];
                dirOri.y*=aspect;
                vec3 crs=cross(look,dirOri);
                vec3 dir=normalize(crs);
                dir.x*=aspect;
                float rad=width;
                pmvPosition.z+=depthOffset;
                if(sizeAttenuation==1){
                    gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset*pmvPosition.w,pmvPosition.w);
                }else{
                    rad*=projection[1][1];
                    gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset,pmvPosition.w);
                }
                vDistance=lineDistance;
                vDrop=count;
                vuv=vec2(lineDistance*uScaleFactor,sign(offset)*0.5);
            }
        `,
            fragmentSource: `
            precision highp float;
            uniform float dashSize;
            uniform float gapSize;
            uniform float opacity;
            uniform int dashed;
            uniform float discardStart;
            uniform float discardEnd;
            uniform vec3 diffuseColor;
            #ifdef USE_DIFFUSE_TEXTURE
                uniform sampler2D diffuseTexture;
            #endif
            varying float vDistance;
            varying float vDrop;
            varying vec2 vuv;
            void main(){
                float md=mod(vDrop,2.0);
                if(md>1.-discardEnd||md < discardStart){
                    discard;
                }
                vec4 tsl=vec4(1.);
                #ifdef USE_DIFFUSE_TEXTURE
                    tsl=texture(diffuseTexture,vec2(vuv.x,vuv.y+0.5));
                #endif
                vec4 col=vec4(diffuseColor.r*tsl.r,diffuseColor.g*tsl.g,diffuseColor.b*tsl.b,tsl.a*opacity);
                if(dashed==0){
                    gl_FragColor=col;
                }else{
                    float dev=mod(vDistance,dashSize+gapSize);
                    if(dev>dashSize){
                        discard;
                    }
                    gl_FragColor=col;
                }
            }`
        }
    }

}


export { StripsLineMaterial };
export type { IStripsLineMaterialOptions };